package IC.Types;

import java.util.HashMap;
import java.util.Map;

import IC.Constants;
import IC.AST.Formal;
import IC.AST.ICClass;
import IC.AST.Method;
import IC.AST.PropagatingVisitor;
import IC.SemanticChecks.SemanticError;
import IC.SemanticChecks.SemanticErrorType;
import IC.SymbolTable.Symbol;
import IC.SymbolTable.SymbolKind;
import IC.SymbolTable.SymbolTable;

/**
 * A static class to hold services used mostly in the type checks.
 */
public class TypeTable {
	  
	private static Map<Type,ArrayType> uniqueArrayTypes = new HashMap<Type,ArrayType>(); 		
	private static Map<String,ClassType> uniqueClassTypes = new HashMap<String,ClassType>(); 	
	private static Map<String,MethodType> uniqueMethodTypes = new HashMap<String,MethodType>(); 
	
	public static Type nullType = new NullType();
	public static Type intType = new IntType();
	public static Type boolType = new BoolType();
	public static Type stringType = new StringType();
	public static Type voidType = new VoidType();
	
	
	public static void createClassType(String Name, String BaseClassName,ICClass cAST) {
		uniqueClassTypes.put(Name,new ClassType(Name,BaseClassName,cAST));
	}
	
	/**
	 * Returns unique class type object
	 */
	public static ClassType classType(String Name) {
		if (uniqueClassTypes.containsKey(Name)) 
			return uniqueClassTypes.get(Name);
		return null;
	}
	
	/**
	 * Returns unique method type object
	 */
	public static MethodType methodType(String Name,Type returnType,Type[] FormalTypes) {
		String methodSignature = MethodType.getSignature(returnType, FormalTypes);
		if (uniqueMethodTypes.containsKey(methodSignature))
			return uniqueMethodTypes.get(methodSignature);
		else 
		{
			MethodType mt = new MethodType(Name,FormalTypes,returnType);
			uniqueMethodTypes.put(methodSignature, mt);
			return mt;
		}
	}
	
	/**
	 * Returns unique array type object
	 */
	public static ArrayType arrayType(Type InnerType) {
		if (uniqueArrayTypes.containsKey(InnerType)) 
			return uniqueArrayTypes.get(InnerType);
		else 
		{
			ArrayType at = new ArrayType(InnerType);
			uniqueArrayTypes.put(InnerType,at);
			return at;
		}
	}
	
	public static void Print(String fileName) {
		
		System.out.println("Type Table: " + fileName);
		System.out.println(Constants.SPACE + intType.getID() + ": Primitive type: " + intType.toString());
		System.out.println(Constants.SPACE + boolType.getID() + ": Primitive type: " + boolType.toString());
		System.out.println(Constants.SPACE + nullType.getID() + ": Primitive type: " + nullType.toString());
		System.out.println(Constants.SPACE + stringType.getID() + ": Primitive type: " + stringType.toString());
		System.out.println(Constants.SPACE + voidType.getID() + ": Primitive type: " + voidType.toString());
		
		for (ClassType ct : uniqueClassTypes.values()) {
			if (ct.getSuperClassName() != null)
				System.out.println(Constants.SPACE + ct.getID() + ": Class: " + ct.toString() + ", Superclass ID: " + classType(ct.getSuperClassName()).getID());
			else 
				System.out.println(Constants.SPACE + ct.getID() + ": Class: " + ct.toString());			
		}
		
		for (ArrayType at : uniqueArrayTypes.values())
			System.out.println(Constants.SPACE + at.getID() + ": Array type: " + at.toString());
		
		for (MethodType mt : uniqueMethodTypes.values())
			System.out.println(Constants.SPACE + mt.getID() + ": Method type: " + mt.toString());
	}
	
	
	/**
	 * Resets the the type table.
	 * Used only when compiling more then one IC file.
	 */
	public static void resetTypeTable() {
		Type.uniqueIdCounter = 6;
		uniqueArrayTypes = new HashMap<Type,ArrayType>();
		uniqueClassTypes = new HashMap<String,ClassType>();
		uniqueMethodTypes = new HashMap<String,MethodType>();
	}
	
	/**
	 * Returns the SymbolTable of the given class
	 */
	public static SymbolTable getClass(String className) {
		ClassType classType = TypeTable.classType(className);
		return (classType == null) ? null : classType.getAst().getScope();
	}
	
	/**
	 * given an Inner type and dimension, creates and returns an arrayType
	 */
	public static Type getArrayType(Type Innertype, int dimension,int lineNumber) {
		Type result = Innertype;
		if (!result.isSuitForArray() && dimension > 0) {
			SemanticError.handle(SemanticErrorType.INVALID_ARRAY_TYPE, lineNumber);
			return null;
		}
		for (int i = 0; i < dimension; i++) 
			result = TypeTable.arrayType(result);
		return result;
	}
	
	/**
	 * given an ASTNode method , creates a methodType and adds it to the given symbol table
	 */
	public static MethodType createMethodType(PropagatingVisitor<SymbolTable,Type> visitor, Method method, SymbolTable d) throws Exception {
		IC.Types.Type[] paramTypes = new IC.Types.Type[method.getFormals().size()];	
		int i = 0;
		for (Formal formal : method.getFormals()) {
			paramTypes[i] = formal.accept(visitor, d);
			i++;
		}
		IC.Types.Type returnType = method.getType().accept(visitor, null);
		Symbol symbol = new Symbol(Constants.RETURN, returnType, SymbolKind.RETURN_VARIABLE);
		d.insertEntry(symbol, method.getLine());
		return TypeTable.methodType(method.getName(), returnType, paramTypes);
	}

	
}
